﻿using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;

namespace winS.UnityEditor.UIElement
{
    /// <summary>
    /// 提供一个泛用的下拉框.
    /// 比 <see cref="GenericDropdownMenu"/> 更好.
    /// </summary>
    public class DropdownBox<T> : VisualElement
    {
        public static readonly float defaultItemHeight = 30f;
        public static readonly float defaultBorderWidth = 1f;
        public static readonly float defaultSplitlineHeight = 1f;
        public static readonly float defaultAnimationDuration = 0.1f;
        public static readonly float defaultMinScrollBarHeightPercent = 0.1f;

        public static readonly Color defaultBorderColor = ColorLevelUtility.GetColor(4);
        public static readonly Color defaultNormalColor = ColorLevelUtility.GetColor(5);
        public static readonly Color defaultScrollBarColor = ColorLevelUtility.GetColor(17);
        public static readonly Color defaultHighlightColor = new Color32(44, 93, 135, 255);

        public event Action onClose;
        public event Action onScrollBarDragEnd;
        public event Action onScrollBarDragStart;
        public event Action<T> onItemMoved;
        public event Action<T> onItemSelected;

        public bool opening { get; private set; }
        /// <summary>
        /// 是否启用自适应.
        /// 当该属性为 true 时,当<see cref="IPanel"/> 空间不足时, 下拉框将尽量上移以显示更多元素;
        /// 否则, 下拉框总是显示在元素下
        /// </summary>
        public bool enableAdaptive { get; set; }
        /// <summary>
        /// 是否有任何溢出的元素.
        /// 如果该属性为true, 则下拉框可进行上下滚动
        /// </summary>
        public bool overflowed { get; private set; }
        public bool keyboardMoveEnabled { get; private set; }

        public float itemHeight { get; private set; }
        /// <summary>
        /// 下拉框的最小宽度
        /// </summary>
        public virtual float minWidth { get; private set; }

        /// <summary>
        /// 获取当前下拉框中可见的元素数量
        /// </summary>
        public int visibleCount => visibleIndexRange.y - visibleIndexRange.x + 1;
        public int selectIndex { get => _selectIndex; set => SetSelectIndex(value); }
        public float animationDuration { get => _animationDuration; set => SetAnimationDuration(value); }

        /// <summary>
        /// 下拉框中可见元素的索引范围
        /// </summary>
        public Vector2Int visibleIndexRange { get; protected set; }

        public Color borderColor { get => _borderColor; set => style.borderBottomColor = style.borderLeftColor = style.borderRightColor = _borderColor = value; }
        public Color highlightColor { get; set; } = defaultHighlightColor;
        public Color backgroundColor { get => _backgroundColor; set => style.backgroundColor = _backgroundColor = value; }
        public Color scrollLineColor { get => _scrollLineColor; set => scrollBar.style.backgroundColor = _scrollLineColor = value; }

        public IList<T> data { get; private set; }
        public Func<T, int, VisualElement> itemDrawFunction { get; private set; }

        protected float realItemHeight => itemHeight + defaultSplitlineHeight;

        protected readonly ItemContainer itemContainer;
        protected readonly List<DropdownItem> dropdownItemList;

        private bool noData => data == null || data.Count == 0;

        private int _selectIndex = -1;

        private float forceWidth = -1f;
        private float dragStartY;
        private float dragStartScrollBarY;
        private float _animationDuration = defaultAnimationDuration;
        private float maxScrollDistance;
        private float maxContentDistance;

        private bool scrollBarDragging;

        private Color _borderColor = defaultBorderColor;
        private Color _backgroundColor = defaultNormalColor;
        private Color _scrollLineColor = defaultScrollBarColor;

        private VisualElement elementToAttach;

        private readonly ScrollBar scrollBar;

        public DropdownBox() : this(null, defaultItemHeight, null, true)
        {
        }
        public DropdownBox(Func<T, int, VisualElement> itemDrawFunction) : this(null, defaultItemHeight, itemDrawFunction, true)
        {
        }
        public DropdownBox(IList<T> data) : this(data, defaultItemHeight, null, true)
        {
        }
        public DropdownBox(IList<T> data, Func<T, int, VisualElement> itemDrawFunction) : this(data, defaultItemHeight, itemDrawFunction, true)
        {
        }
        public DropdownBox(IList<T> data, float itemHeight, Func<T, int, VisualElement> itemDrawFunction, bool enableAdaptive)
        {
            this.data = data;
            this.itemHeight = itemHeight;
            this.enableAdaptive = enableAdaptive;
            this.itemDrawFunction = itemDrawFunction;

            RegisterCallback<WheelEvent>(WheelCallback);
            RegisterCallback<MouseUpEvent>(MouseUpCallback);
            RegisterCallback<FocusOutEvent>(FocusOutCallback);
            RegisterCallback<MouseDownEvent>(MouseDownCallback);
            RegisterCallback<MouseMoveEvent>(MouseMoveCallback);

            focusable = true;
            style.height = 0f;
            style.overflow = Overflow.Hidden;
            style.position = Position.Absolute;
            style.SetBorder(_borderColor, defaultBorderWidth);
            style.SetBackgroundColor(backgroundColor);
            style.SetTransition("height", _animationDuration, EasingMode.EaseOutCirc);

            dropdownItemList = new List<DropdownItem>(data == null ? 0 : data.Count);

            Add(itemContainer = new ItemContainer());

            Add(scrollBar = new ScrollBar(defaultScrollBarColor));
            scrollBar.style.right = 0f;
            scrollBar.style.position = Position.Absolute;
            scrollBar.style.SetWidth(4f, LengthUnit.Percent);

            Refresh();
        }

        public void EnableKeyboardMove()
        {
            keyboardMoveEnabled = true;
            RegisterCallback<KeyDownEvent>(KeyDownCallback);
        }
        public void DisableKeyboardMove()
        {
            keyboardMoveEnabled = false;
            UnregisterCallback<KeyDownEvent>(KeyDownCallback);
        }
        /// <summary>
        /// 上移一个选项并选择
        /// </summary>
        public void Up()
        {
            if (noData) return;
            int newIndex = selectIndex - 1;
            if (!overflowed)
            {
                selectIndex = newIndex < 0 ? data.Count - 1 : newIndex;
                goto end;
            }

            int maxIndex = data.Count - 1;
            if (newIndex < 0)
            {
                itemContainer.position = maxContentDistance;
                selectIndex = maxIndex;
                visibleIndexRange = new Vector2Int(maxIndex - visibleCount + 1, maxIndex);
            }
            else
            {
                selectIndex = newIndex;
                if (CheckElementVisibility(newIndex)) goto end;
                itemContainer.position += realItemHeight;
                visibleIndexRange -= Vector2Int.one;
            }
            scrollBar.position = itemContainer.position / maxContentDistance * maxScrollDistance;
        end:
            onItemMoved?.Invoke(data[selectIndex]);
        }
        /// <summary>
        /// 下移一个选项并选择
        /// </summary>
        public void Down()
        {
            if (noData) return;
            int newIndex = selectIndex + 1;
            if (!overflowed)
            {
                selectIndex = newIndex > data.Count - 1 ? 0 : newIndex;
                goto end;
            }

            int maxIndex = data.Count - 1;
            if (newIndex > maxIndex)
            {
                itemContainer.position = selectIndex = 0;
                visibleIndexRange = new Vector2Int(0, visibleCount - 1);
            }
            else
            {
                selectIndex = newIndex;
                if (CheckElementVisibility(newIndex)) goto end;
                itemContainer.position -= realItemHeight;
                visibleIndexRange += Vector2Int.one;
            }
            scrollBar.position = itemContainer.position / maxContentDistance * maxScrollDistance;
        end:
            onItemMoved?.Invoke(data[selectIndex]);
        }
        /// <summary>
        /// 显示下拉框并作为前台元素
        /// </summary>
        public void Show(VisualElement elementToAttach)
        {
            ShowInBackground(elementToAttach);
            this.CaptureMouse();
            Focus();
        }
        public void Show(VisualElement elementToAttach, float forceWidth)
        {
            Show(elementToAttach);
            if (forceWidth < 0f) return;
            this.forceWidth = forceWidth;
            itemContainer.style.width = StyleKeyword.Auto;
        }
        /// <summary>
        /// 显示下拉框但作为后台元素
        /// </summary>
        public void ShowInBackground(VisualElement elementToAttach)
        {
            if (noData) return;
            opening = true;
            forceWidth = -1f;
            this.elementToAttach = elementToAttach;
            IPanel panel = elementToAttach.panel;
            VisualElement root = panel.visualTree.ElementAt(1);
            root.Add(this);
            Vector2 point = new Vector2(0, elementToAttach.layout.height);
            Vector2 position = elementToAttach.ChangeCoordinatesTo(root, point);
            this.SetPosition(position + new Vector2(0f, 1f));
            schedule.Execute(RecalculateAll).StartingIn(10);//不能立刻计算, 当前帧刚附加到面板元素上, 立即设置没有数据,也不会触发过渡动画
        }
        public void ShowInBackground(VisualElement elementToAttach, float forceWidth)
        {
            ShowInBackground(elementToAttach);
            if (forceWidth < 0f) return;
            this.forceWidth = forceWidth;
            itemContainer.style.width = StyleKeyword.Auto;
        }
        public void Close()
        {
            if (!opening) return;
            //必须从Panel中移除而非 style.display = DisplayStyle.None; 如果下拉项很多, 会有大量重绘的性能损耗
            panel.visualTree.ElementAt(1).Remove(this);
            opening = false;
            style.height = 0f;

            if (selectIndex != -1)
            {
                dropdownItemList[selectIndex].SetBackgroundColor(Color.clear);
                selectIndex = -1;
            }
            scrollBar.DisableImmediately();
            this.ReleaseMouse();
            onClose?.Invoke();
        }
        public void Refresh()
        {
            if (itemDrawFunction == null || noData) return;

            //复用已有元素
            if (dropdownItemList.Count < data.Count)
            {
                if (dropdownItemList.Capacity < data.Count) dropdownItemList.Capacity = data.Count;
                int i = 0;
                for (; i < dropdownItemList.Count; i++) dropdownItemList[i].Update(itemDrawFunction(data[i], i));
                for (; i < data.Count; i++) CreateAndAddDropdownItem(data[i], i);
            }
            else
            {
                int i = 0;
                for (; i < data.Count; i++) dropdownItemList[i].Update(itemDrawFunction(data[i], i));
                int redundantCount = dropdownItemList.Count - data.Count;
                if (redundantCount == 0) return;
                dropdownItemList.RemoveRange(i, redundantCount);
                int currentIndex = itemContainer.childCount - 1;
                while (redundantCount > 0)
                {
                    itemContainer.RemoveAt(currentIndex);
                    currentIndex--;
                    redundantCount--;
                }
            }
        }
        public void SetData(IList<T> data, bool refresh = true)
        {
            this.data = data;
            if (noData) return;
            if (selectIndex > data.Count - 1) selectIndex = -1;
            if (refresh) Refresh();
        }
        public void SetItemDrawFunction(Func<T, int, VisualElement> itemDrawFunction, bool refresh = true)
        {
            if (itemDrawFunction == null) return;
            bool isFirstTime = this.itemDrawFunction == null;
            this.itemDrawFunction = itemDrawFunction;
            if (refresh)
            {
                if (isFirstTime && !noData) for (int i = 0; i < data.Count; i++) CreateAndAddDropdownItem(data[i], i);
                else for (int i = 0; i < dropdownItemList.Count; i++) dropdownItemList[i].Update(itemDrawFunction(data[i], i));
            }
        }
        /// <summary>
        /// 重新计算所有与视图有关的数据.
        /// 这会重置所有数据并且重新计算高度和宽度 
        /// </summary>
        public virtual void RecalculateAll()
        {
            if (panel == null) return;
            overflowed = false;
            scrollBar.style.height = itemContainer.position = scrollBar.position = maxScrollDistance = maxContentDistance = 0;
            visibleIndexRange = Vector2Int.zero;
            RecalculateHeight();
            RecalculateWidth();
        }
        public void RecalculateWidth()
        {
            if (forceWidth > 0f)
            {
                itemContainer.style.width = forceWidth;
                style.width = style.borderLeftWidth.value + style.borderRightWidth.value + forceWidth;
                return;
            }
            float preferredWidth = Math.Max(elementToAttach.layout.width, itemContainer.layout.width);
            if (preferredWidth < minWidth)
            {
                itemContainer.style.width = minWidth;
                style.width = style.borderLeftWidth.value + style.borderRightWidth.value + minWidth;
                return;
            }
            itemContainer.style.width = preferredWidth;
            style.width = preferredWidth + style.borderLeftWidth.value + style.borderRightWidth.value;
        }
        public void RecalculateHeight()
        {
            float currentY = layout.y;
            float availableHeight = panel.visualTree.ElementAt(1).layout.height - currentY;
            float contentHeight = itemContainer.layout.height;

            int maxIndex = dropdownItemList.Count - 1;
            if (contentHeight > availableHeight)//向下高度溢出
            {
                overflowed = true;
                if (enableAdaptive)
                {
                    //首先尽量上移
                    float overflowHeight = contentHeight - availableHeight;
                    float newY = currentY - overflowHeight;
                    if (newY < 0f)//上移空间不足, 上移后依旧溢出
                    {
                        availableHeight += overflowHeight + newY;
                        style.top = 0f;
                    }
                    else
                    {
                        style.top = newY;
                        overflowed = false;
                    }
                }
                if (overflowed)
                {
                    int capacity = (int)(availableHeight / realItemHeight);
                    float viewportHeight = capacity * realItemHeight;
                    maxContentDistance = -(contentHeight - viewportHeight);
                    contentHeight = viewportHeight;

                    float percent = (float)capacity / dropdownItemList.Count;
                    float scrollLineHeight = Mathf.Max(defaultMinScrollBarHeightPercent, percent) * contentHeight;
                    scrollBar.style.height = scrollLineHeight;
                    maxScrollDistance = contentHeight - scrollLineHeight;
                    maxIndex = capacity - 1;
                }
            }

            visibleIndexRange = new Vector2Int(0, maxIndex);
            style.height = contentHeight + defaultBorderWidth;
        }
        /// <summary>
        /// 检查索引为 <paramref name="index"/> 的元素是否可见.
        /// 如果该元素位于可见区域外, 返回 false
        /// </summary>
        public bool CheckElementVisibility(int index)
        {
            if (!overflowed) return true;
            return index >= visibleIndexRange.x && index <= visibleIndexRange.y;
        }

        private void SetSelectIndex(int newSelectIndex)
        {
            if (_selectIndex == newSelectIndex) return;
            if (_selectIndex != -1) dropdownItemList[_selectIndex].SetBackgroundColor(Color.clear);
            if ((_selectIndex = newSelectIndex) < 0) return;
            dropdownItemList[_selectIndex].SetBackgroundColor(highlightColor);
        }
        private void SetAnimationDuration(float newAnimationDuration)
        {
            _animationDuration = newAnimationDuration;
            style.SetTransition("height", _animationDuration);
        }
        private DropdownItem CreateAndAddDropdownItem(T item, int index)
        {
            DropdownItem dropdownItem = new DropdownItem();
            dropdownItem.style.SetMinHeight(realItemHeight);
            dropdownItem.Add(itemDrawFunction(item, index));
            dropdownItemList.Add(dropdownItem);
            itemContainer.Add(dropdownItem);
            return dropdownItem;
        }

        private void WheelCallback(WheelEvent wheelEvent)
        {
            if (!overflowed) return;
            itemContainer.position = Mathf.Clamp(itemContainer.position - Mathf.Sign(wheelEvent.delta.y) * realItemHeight, maxContentDistance, 0f);
            scrollBar.position = itemContainer.position / maxContentDistance * maxScrollDistance;
        }
        private void KeyDownCallback(KeyDownEvent keyDownEvent)
        {
            if (!opening) return;
            switch (keyDownEvent.keyCode)
            {
                case KeyCode.Return:
                    onItemSelected?.Invoke(data[selectIndex]);
                    Close();
                    break;
                case KeyCode.UpArrow: Up(); break;
                case KeyCode.DownArrow: Down(); break;
                default: return;
            }
            keyDownEvent.StopImmediatePropagation();
        }
        private void MouseUpCallback(MouseUpEvent mouseUpEvent)
        {
            if (!scrollBarDragging) return;
            scrollBarDragging = false;
            scrollBar.Disable();
            onScrollBarDragEnd?.Invoke();
        }
        private void MouseDownCallback(MouseDownEvent mouseDownEvent)
        {
            Vector2 localMousePosition = mouseDownEvent.localMousePosition;
            Rect layout = this.layout;
            if (localMousePosition.y <= 0 || localMousePosition.x <= 0 || localMousePosition.x >= layout.width || localMousePosition.y >= layout.height - defaultBorderWidth)
            {
                Close();
                return;
            }
            //检查是否按在滚动条上
            if (overflowed && scrollBar.ContainsPoint(this.ChangeCoordinatesTo(scrollBar, localMousePosition)))
            {
                scrollBar.Enable();
                scrollBarDragging = true;
                dragStartY = localMousePosition.y;
                dragStartScrollBarY = scrollBar.position;
                onScrollBarDragStart?.Invoke();
                this.CaptureMouse();
                Focus();
                return;
            }

            int newSelectIndex = (int)((localMousePosition.y - itemContainer.position) / realItemHeight);
            onItemSelected?.Invoke(data[newSelectIndex]);
            mouseDownEvent.StopImmediatePropagation();
            Close();
        }
        private void MouseMoveCallback(MouseMoveEvent mouseMoveEvent)
        {
            Vector2 localMousePosition = mouseMoveEvent.localMousePosition;
            if (scrollBarDragging)
            {
                float distanceDelta = localMousePosition.y - dragStartY;
                float newPosition = Mathf.Clamp(dragStartScrollBarY + distanceDelta, 0f, maxScrollDistance);
                scrollBar.position = newPosition;
                float containerNewPosition = newPosition / maxScrollDistance * maxContentDistance;
                itemContainer.position = containerNewPosition;
                containerNewPosition = -containerNewPosition;
                float remainder = containerNewPosition % realItemHeight;
                int startVisibleIndex = (int)(containerNewPosition / realItemHeight);
                if (remainder >= realItemHeight * 0.5f) startVisibleIndex += 1;
                visibleIndexRange = new Vector2Int(startVisibleIndex, startVisibleIndex + visibleCount - 1);
                return;
            }
            Rect layout = this.layout;
            if (localMousePosition.y <= 0 || localMousePosition.x <= 0 || localMousePosition.x >= layout.width || localMousePosition.y >= layout.height - defaultBorderWidth) return;

            int newSelectIndex = (int)((localMousePosition.y - itemContainer.position) / realItemHeight);
            if (selectIndex == newSelectIndex) return;
            selectIndex = newSelectIndex;
            onItemMoved?.Invoke(data[selectIndex]);
        }
        private void FocusOutCallback(FocusOutEvent focusOutEvent)
        {
            Close();
        }

        public class ScrollBar : VisualElement
        {
            public static readonly float defaultRightValue = -94f;
            public static readonly float defaultAnimationDelay = 0.6f;

            public bool enabled { get; private set; }
            public bool hovering { get; private set; }

            public float position { get => style.top.value.value; set => style.top = value; }

            private readonly IVisualElementScheduledItem animationSchedule;
            private readonly VisualElement scrollBar;

            public ScrollBar(Color scrollBarColor)
            {
                Add(scrollBar = EditorElementUtility.elementFactory.CreateEditorElement(scrollBarColor));
                scrollBar.pickingMode = PickingMode.Ignore;
                scrollBar.style.right = 0f;
                scrollBar.style.minWidth = 1f;
                scrollBar.style.flexGrow = 1f;
                scrollBar.style.SetRight(defaultRightValue, LengthUnit.Percent);
                scrollBar.style.SetTransition("right", 0.1f);

                animationSchedule = schedule.Execute(() => scrollBar.style.SetRight(defaultRightValue, LengthUnit.Percent));
                animationSchedule.Pause();

                RegisterCallback<MouseLeaveEvent>(MouseLeaveCallback);
                RegisterCallback<MouseEnterEvent>(MouseEnterCallback);
            }

            public void Enable()
            {
                enabled = true;
                animationSchedule.Pause();
            }
            public void Disable()
            {
                enabled = false;
                if (hovering) return;
                animationSchedule.ExecuteLater((long)(defaultAnimationDelay * 1000f));
            }
            public void DisableImmediately()
            {
                scrollBar.style.SetRight(defaultRightValue, LengthUnit.Percent);
            }

            private void MouseLeaveCallback(MouseLeaveEvent mouseLeaveEvent)
            {
                hovering = false;
                if (enabled) return;
                animationSchedule.ExecuteLater((long)(defaultAnimationDelay * 1000f));
            }
            private void MouseEnterCallback(MouseEnterEvent mouseEnterEvent)
            {
                hovering = true;
                animationSchedule.Pause();
                scrollBar.style.SetRight(0f, LengthUnit.Percent);
            }
        }
        public class DropdownItem : VisualElement
        {
            internal static Color borderColor = new Color32(36, 36, 36, 255);

            public VisualElement content => ElementAt(0);

            internal DropdownItem()
            {
                style.flexDirection = FlexDirection.Row;
                style.SetBorderBottom(1f, borderColor);
            }

            public void Update(VisualElement element)
            {
                RemoveAt(0);
                Add(element);
            }
            public void SetBackgroundColor(Color color)
            {
                style.SetBackgroundColor(color);
            }
        }
        public class ItemContainer : VisualElement
        {
            public float position { get => style.top.value.value; set => style.top = value; }

            public ItemContainer()
            {
                style.position = Position.Absolute;
            }
        }
    }
}